package lumis.portal.serialization;

import lumis.portal.PortalException;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.serialization.operation.ConfigDeletion;
import lumis.portal.serialization.operation.ConfigForeignKey;
import lumis.portal.serialization.operation.ConfigPersistence;
import lumis.portal.serialization.operation.DependentConfigOperationCollection;
import lumis.portal.serialization.persistency.IDirectory;
import lumis.portal.serviceinstance.serialization.ServiceInstanceDeserializationContext;
import lumis.util.ITransaction;
import lumis.util.PortalUtil;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Context class for portal objects deserialization
 *
 * @version $Revision: 11953 $ $Date: 2010-09-22 09:51:35 -0300 (Wed, 22 Sep 2010) $
 * @since 4.0.7
 */
public class DeserializationContext extends BaseSerializationContext
{
	public enum ExistingStyleAction
	{
		ABORT, UPDATE, SKIP;

		public int toInt()
		{
			switch(this)
			{
				case ABORT: return 0;
				case UPDATE: return 1;
				case SKIP: return 2;
				default: throw new IllegalArgumentException("Invalid ExistingStyleAction enum value.");
			}
		}

		public static ExistingStyleAction fromInt(int value)
		{
			switch(value)
			{
				case 0: return ABORT;
				case 1: return UPDATE;
				case 2: return SKIP;
				default: throw new IllegalArgumentException("Invalid ExistingStyleAction int value.");
			}
		}
	}

	public static class ImportMemberRules
	{
		public static enum ForCreate
		{
			ALL_MEMBERS,
			ALL_MEMBERS_OF_TYPE_GROUP,
			ALL_MEMBERS_OF_TYPE_GROUP_IGNORING_NON_EXISTENT_GROUPS,
			ONLY_LOCAL_MEMBERS_OF_TYPE_GROUP
		}

		public static enum ForUpdate
		{
			ALL_MEMBERS,
			ALL_MEMBERS_OF_TYPE_GROUP,
			ALL_MEMBERS_OF_TYPE_GROUP_IGNORING_NON_EXISTENT_GROUPS,
			ONLY_LOCAL_MEMBERS_OF_TYPE_GROUP
		}

		private ForCreate forCreate = ForCreate.ALL_MEMBERS_OF_TYPE_GROUP;
		private ForUpdate forUpdate = ForUpdate.ONLY_LOCAL_MEMBERS_OF_TYPE_GROUP;

		public ForCreate getForCreate()
		{
			return forCreate;
		}
		public void setForCreate(ForCreate forCreate)
		{
			this.forCreate = forCreate;
		}
		public ForUpdate getForUpdate()
		{
			return forUpdate;
		}
		public void setForUpdate(ForUpdate forUpdate)
		{
			this.forUpdate = forUpdate;
		}
	}

	private final Map<String, String> localGroupPrefixMappings = new HashMap<String, String>();

	private final Map<String, String> localGroupPrefixOriginalMappings = new HashMap<String, String>();
	private final Map<String, String> idMappings = new HashMap<String, String>();
	private final DependentConfigOperationCollection<ConfigPersistence> dependentConfigPersistences = new DependentConfigOperationCollection<ConfigPersistence>();
	private final boolean createNewIds;
	private final boolean updateIfElementExists;
	private final boolean deleteExistingElements;
	private final boolean importAsChild;
	private final boolean simulation;
	private final ExistingStyleAction existingStyleAction;
	private boolean mustRollBack = false;
	private final Map<String, ConfigDeletion> configDeletions = new HashMap<String, ConfigDeletion>();
	private final ServiceInstanceDeserializationContext serviceInstanceDeserializationContext;
	private ImportMemberRules importMemberRules = new ImportMemberRules();

	public DeserializationContext(SessionConfig sessionConfig, ITransaction transaction, String rootChannelId, IDirectory directory, boolean createNewIds, boolean updateIfElementExists,
			boolean importAsChild, boolean deleteExistingElements, boolean simulation, ExistingStyleAction existingStyleAction, boolean progressEnabled, boolean includeContent)
	{
		super(sessionConfig, transaction, rootChannelId, directory, progressEnabled, includeContent);
		this.createNewIds = createNewIds;
		this.updateIfElementExists = updateIfElementExists;
		this.importAsChild = importAsChild;
		this.deleteExistingElements = deleteExistingElements;
		this.simulation = simulation;
		this.existingStyleAction = existingStyleAction;
		this.serviceInstanceDeserializationContext = new ServiceInstanceDeserializationContext(this);
		if (simulation)
			mustRollBack = true;
	}

	@Override
	public void setTransaction(ITransaction transaction)
	{
		super.setTransaction(transaction);
		transaction.setAttribute(ISerializationManager2.ATTRIBUTE_DESERIALIZING, Boolean.TRUE);
	}

	public void setImportMemberRules(ImportMemberRules importMemberRules)
	{
		this.importMemberRules = importMemberRules;
	}

	public ImportMemberRules getImportMemberRules()
	{
		return this.importMemberRules;
	}

	public boolean isCreateNewIds()
	{
		return createNewIds;
	}

	public void addIdMapping(String id, String newId)
	{
		if (id == null)
			throw new IllegalArgumentException("id cannot be null");
		idMappings.put(id, newId);
	}

	public DependentConfigOperationCollection<ConfigPersistence> getDependentConfigPersistences()
	{
		return dependentConfigPersistences;
	}

	public boolean isUpdateIfElementExists()
	{
		return updateIfElementExists;
	}

	public boolean isImportAsChild()
	{
		return importAsChild;
	}

	public String getIdMapping(String originalId)
	{
		if (originalId == null)
			return null;
		String mappedId = idMappings.get(originalId);
		if (mappedId != null)
			return mappedId;
		else
			return originalId;
	}

	public boolean hasIdMapping(String originalId)
	{
		return idMappings.containsKey(originalId);
	}

	public void applyForeignKeyFieldIdMappings(Object target, List<ConfigForeignKey> foreignKeys) throws SerializationException
	{
		ArrayList<String> fieldNames = new ArrayList<String>();
		for (ConfigForeignKey configForeignKey : foreignKeys)
		{
			if (configForeignKey.isMutable())
				fieldNames.add(configForeignKey.getName());
		}
		applyForeignKeyFieldIdMappings(target, fieldNames.toArray(new String[0]));
	}
	public void applyForeignKeyFieldIdMappings(Object target, String[] fieldNames) throws SerializationException
	{
		try
		{
			Class<?> targetClass = target.getClass();
			for (String fieldName : fieldNames)
			{
				Field field = getField(targetClass, fieldName);
				if(field != null)
				{
					field.setAccessible(true);
					String value = (String) field.get(target);
					field.set(target, getIdMapping(value));
				}
				else
				{
					throw new Exception("Field not found: "+fieldName+" on target class or super classes. Target class: "+ target.getClass().getCanonicalName());
				}
			}
		}
		catch (Exception e)
		{
			throw new SerializationException("ERROR_APPLYING_ID_MAPPINGS", e);
		}
	}

	private Field getField(Class<?> targetClass, String fieldName)
	{
		if(targetClass == null)
			throw new IllegalArgumentException("targetClass cannot be null");

		Field field;
		try
		{
			field = targetClass.getDeclaredField(fieldName);
		}
		catch (NoSuchFieldException e)
		{
			field = getField(targetClass.getSuperclass(), fieldName);
		}
		return field;
	}

	public void applyPrimaryKeyFieldIdMappings(Object target, String[] fieldNames) throws SerializationException
	{
		logDebug("Before PK mappings:", target);

		try
		{
			Class<?> targetClass = target.getClass();
			for (String newIdFieldName : fieldNames)
			{
				Field field = targetClass.getDeclaredField(newIdFieldName);
				field.setAccessible(true);
				String idValue = (String) field.get(target);
				String newIdValue = getIdMapping(idValue);
				if (newIdValue.equals(idValue) && createNewIds)
				{
					newIdValue = PortalUtil.generateNewGuid();
					idMappings.put(idValue, newIdValue);
				}
				field.set(target, newIdValue);
			}
		}
		catch (Exception e)
		{
			throw new SerializationException("ERROR_APPLYING_ID_MAPPINGS", e);
		}

		logDebug("After PK mappings:", target);
	}

	public void persistAllDependentConfigs() throws PortalException
	{
		logDebug("Performing all pendent config persistences");
		setItemProgressMax(getConfigDeletions().size());
		setItemProgressValue(0);
		for (String dependencyKey : dependentConfigPersistences.getDependentKeys())
		{
			updateProgressBar(0, 1, "STR_PERSISTING_REMAINING_DEPENDENT_OBJECTS");
			persistDependentConfigs(dependencyKey);
		}
	}

	public void persistDependentConfigs(String dependencyKey) throws PortalException
	{
		logDebug("Performing config persistences that depends on '" + dependencyKey + "'");
		for (ConfigPersistence configPersistence : dependentConfigPersistences.popDependentConfigOperations(dependencyKey))
		{
			logDebug("Performing config persistence: IPersister:" + configPersistence.getClass().getName() + " config:" , configPersistence.getConfig());
			if (!configPersistence.getConfigPersister().persistConfig(this, configPersistence.getConfig(), false))
			{
				logDebug("Config persistence has failed. It will remain as a pendent persistence:", configPersistence.getConfig());
				dependentConfigPersistences.addDependentConfigOperation(dependencyKey, configPersistence);
			}
		}
	}

	public boolean isMustRollBack()
	{
		return mustRollBack;
	}

	public void setMustRollBack()
	{
		this.mustRollBack = true;
	}

	public boolean isDeleteExistingElements()
	{
		return deleteExistingElements;
	}

	public boolean isSimulation()
	{
		return simulation;
	}

	public ExistingStyleAction getExistingStyleAction()
	{
		return existingStyleAction;
	}

	public Map<String, ConfigDeletion> getConfigDeletions()
	{
		return configDeletions;
	}

	public void addConfigDeletion(ConfigDeletion configDeletion)
	{
		configDeletions.put(configDeletion.getConfigId(), configDeletion);
	}

	public void removeConfigDeletion(String configId)
	{
		configDeletions.remove(configId);
	}
	public ServiceInstanceDeserializationContext getServiceInstanceDeserializationContext()
	{
		return serviceInstanceDeserializationContext;
	}

	public void addLocalGroupPrefixMapping(String original, String mapped)
	{
		localGroupPrefixMappings.put(original, mapped);
		localGroupPrefixOriginalMappings.put(mapped, original);
	}

	public String getLocalGroupPrefixMapping(String original)
	{
		return localGroupPrefixMappings.get(original);
	}

	public String getOriginalGroupPrefix(String mapping)
	{
		return localGroupPrefixOriginalMappings.get(mapping);
	}
}
